/*
 * Copyright (c) 2009, Hamish Morgan. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the University of Sussex nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */
package locusts.server;

import java.awt.geom.Area;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import locusts.client.ClientConfig;
import locusts.common.Constants;
import locusts.common.Player;
import locusts.common.entities.CollidableEntity;
import locusts.common.entities.Entity;
import locusts.common.net.FullUpdateRequest;
import locusts.common.net.InputMessage;
import locusts.common.net.PlayerMessage;
import locusts.common.net.PlayerMessage.NewPlayerRequest;
import locusts.common.net.PlayerMessage.NewPlayerResponse;
import locusts.common.net.PlayerMessage.PlayerDetailsUpdate;
import locusts.common.net.WorldUpdateMessage;
import locusts.common.vibro.VTAccumulator;
import locusts.common.vibro.VTMessage;
import org.apache.mina.common.IoSession;
import org.apache.mina.handler.demux.MessageHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Hamish Morgan
 */
public class Server implements GameListener {

    private static final Logger LOG = LoggerFactory.getLogger(Server.class);
    private final Game game;
    private final ServerNetHandler netHandler;
    private VTMessage vtmessage = null;
    private Map<IoSession, ClientConfig> clientConfigs;

    public Server(Game g, int port) throws IOException {
        clientConfigs = new HashMap<IoSession, ClientConfig>();
        this.game = g;
        this.game.addGameListener(this);
        this.netHandler = new ServerNetHandler(port);

        netHandler.addMessageHandler(InputMessage.class,
                game.getController().inputHandler);


        netHandler.addMessageHandler(PlayerMessage.class,
                new PlayerMessageHandler());
//        netHandler.addMessageHandler(Player.class, new MessageHandler<Player>() {
//
//            public void messageReceived(IoSession session, Player e)
//                    throws Exception {
//                Player p = new Player(e.getName(), e.getScore(), e.getCrops());
//                LOG.info("Player joined: {}", e);
//                game.addPlayer(e);
//                session.write(p);
//                session.setAttribute("Player Data Sent");
//            }
//        });

        netHandler.addMessageHandler(FullUpdateRequest.class,
                new FullUpdateRequestHandler());
        netHandler.addMessageHandler(ClientConfig.class,
                new ClientConfigHandler());

        netHandler.start();


        game.getPlayers().addListDataListener(new ListDataListener() {

            public void intervalAdded(ListDataEvent e) {
                netHandler.broadcast(game.getPlayers());
            }

            public void intervalRemoved(ListDataEvent e) {
                netHandler.broadcast(game.getPlayers());
            }

            public void contentsChanged(ListDataEvent e) {
                netHandler.broadcast(game.getPlayers());
            }
        });
    }

    public Map<IoSession, ClientConfig> getClientConfigs() {
        return clientConfigs;
    }

    private class FullUpdateRequestHandler implements
            MessageHandler<FullUpdateRequest> {

        public void messageReceived(IoSession session, FullUpdateRequest e)
                throws Exception {
            LOG.info("Full Update Requested: {}", e);
            session.write(WorldUpdateMessage.createFrom(game.getWorld(),
                    true));
        }
    }

    private class ClientConfigHandler implements MessageHandler<ClientConfig> {

        public void messageReceived(IoSession session, ClientConfig message)
                throws Exception {
            if (clientConfigs.containsKey(session)) {
                final ClientConfig current = clientConfigs.get(session);
                current.set(message);
                current.addChangeListener(new ClientConfigChangeHandler(
                        session));
            } else {
                clientConfigs.put(session, message);
                message.addChangeListener(new ClientConfigChangeHandler(
                        session));
            }
        }

        public class ClientConfigChangeHandler implements ChangeListener {

            private IoSession session;

            public ClientConfigChangeHandler(IoSession session) {
                this.session = session;
            }

            public void stateChanged(ChangeEvent e) {
//                System.out.println("Sending state change update.");
//                System.out.println(e.getSource().getClass());
//                System.out.println(
//                        "fireStateChangeEvent stateChanged ========================================");

                netHandler.broadcast(e.getSource());
//                session.write(e.getSource());
            }
        }
    }

    private class PlayerMessageHandler implements
            MessageHandler<PlayerMessage> {

        public void messageReceived(IoSession session, PlayerMessage message)
                throws Exception {
            if (message.getClass().equals(NewPlayerRequest.class)) {
                handleMessage(session, (NewPlayerRequest) message);
            } else if (message.getClass().equals(NewPlayerResponse.class)) {
                handleMessage(session, (NewPlayerResponse) message);
            } else if (message.getClass().equals(PlayerDetailsUpdate.class)) {
                handleMessage(session, (PlayerDetailsUpdate) message);
            }
//            throw new UnsupportedOperationException("Not supported yet.");
        }

        public void handleMessage(IoSession session, NewPlayerRequest message) {
            Player p = new Player();
            LOG.info("Player joined: {}", p);
            game.addPlayer(p);
            session.write(new NewPlayerResponse(message.getLocalId(), p));
//            session.write(p);
//            session.setAttribute("Player Data Sent");
        }

        public void handleMessage(IoSession session, NewPlayerResponse message) {
            // should not be recieved by a server ever!
        }

        public void handleMessage(IoSession session,
                PlayerDetailsUpdate message) {
            List<Player> players = game.getPlayers();
            for (Player player : players) {
                if (player.getId() == message.getPlayer().getId()) {
                    message.applyTo(player);
                    return;
                }
            }
            LOG.warn("Player not found for update message.");
        }
    }

    public Game getGame() {
        return game;
    }

    public ServerNetHandler getNetHandler() {
        return netHandler;
    }

    public void gameUpdated(Game game) {

        netHandler.broadcast(
                WorldUpdateMessage.createFrom(game.getWorld(), false));

        for (Player player : game.getPlayers()) {
            if (!player.isModified())
                continue;
            netHandler.broadcast(new PlayerDetailsUpdate(player));
            player.setModified(false);
        }


        final VTMessage m = makeVTMessage(game);
        if (!m.equals(vtmessage)) {
            netHandler.broadcast(m);
            vtmessage = m;
        }
    }

    private static VTMessage makeVTMessage(Game game) {
        // create a accumulator for quantising the input entities
        VTAccumulator vta = new VTAccumulator(4, 4, game.getWorld().getWidth(), game.getWorld().
                getHeight());
        int downId =
                game.getEntityTypes().getName(Constants.MOUSE_DOWN).getId();
        int moveId =
                game.getEntityTypes().getName(Constants.MOUSE_MOVE).getId();
        final long time = System.currentTimeMillis();
        final long minAge = 500;
        for (Entity e : game.getEntities()) {
            if (e.getTypeId() != downId && e.getTypeId() != moveId)
                continue;
            if (e.getCreationTime() + minAge < time)
                continue;
            
            final
             Area area;
            if (e instanceof CollidableEntity)
                area = ((CollidableEntity) e).geTransformedArea();
            else
                area = game.getEntityData().get(e).getArea().
                        createTransformedArea(e.getAffineTransform());
            vta.add(area);
        }
        VTMessage m = VTMessage.createFrom(vta);
        return m;
    }

    public void playerJoined(Game game, Player player) {
        netHandler.broadcast(WorldUpdateMessage.createFrom(game.getWorld(),
                true));
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void playerLeft(Game game, Player player) {
        netHandler.broadcast(WorldUpdateMessage.createFrom(game.getWorld(),
                true));
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void gameEntitiesAdded(Game game) {
//        throw new UnsupportedOperationException("Not supported yet.");
    }

    public void gameEntitiesRemoved(Game game) {
//        throw new UnsupportedOperationException("Not supported yet.");
    }
}
